package com.chuangyiruanke.util;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

public class ZipUtil {
    // 缓冲区大小为 1M
    private static final int BUFFER_LEN = 1048576;

    public static void compress(String fileOrDirectory, OutputStream outputStream) throws IOException {
        ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream, StandardCharsets.UTF_8);
        File file = new File(fileOrDirectory);
        addCompressFile("", file, zipOutputStream);
        zipOutputStream.finish();
        zipOutputStream.close();
    }

    public static void compress(OutputStream outputStream, String... files) throws IOException {
        ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream, StandardCharsets.UTF_8);
        String baseDir = findBaseDir(files);
        Map<String, File> map = new HashMap<>();
        for (String fileName : files) {
            File file = new File(fileName);
            if (map.containsKey(file.getName())) {
                continue;
            }
            map.put(fileName, file);
            addCompressFile(file.getParentFile().getPath().substring(baseDir.length()), file, zipOutputStream);
        }
        zipOutputStream.finish();
        zipOutputStream.close();
    }

    public static List<String> decompress(String sourceFile, String dest) throws IOException {
        ZipFile zipFile = new ZipFile(sourceFile, StandardCharsets.UTF_8);
        File destDir = new File(dest);
        if (!destDir.exists() && !destDir.mkdirs()) {
            throw new IOException("未能建立指定路径中的一部分文件夹！");
        }
        Enumeration<? extends ZipEntry> enumeration = zipFile.entries();
        byte[] buffer = new byte[BUFFER_LEN];
        List<String> files = new ArrayList<>();
        while (enumeration.hasMoreElements()) {
            ZipEntry entry = enumeration.nextElement();
            File destFile = new File(destDir, entry.getName());
            if (entry.isDirectory() && !destFile.exists()) {
                destFile.mkdirs();
                continue;
            }
            if (!destFile.getParentFile().exists() && !destFile.getParentFile().mkdirs()) {
                throw new IOException("未能建立指定路径中的一部分文件夹！");
            }
            OutputStream outputStream = new FileOutputStream(destFile);
            InputStream inputStream = zipFile.getInputStream(entry);
            int len;
            while ((len = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }
            files.add(destFile.getAbsolutePath());
            inputStream.close();
            outputStream.close();
        }
        zipFile.close();
        return files;
    }

    private static String findBaseDir(String[] files) {
        String baseDir = null;
        if (files == null || files.length == 0) {
            return "";
        }
        for (String fileName : files) {
            if (baseDir == null || baseDir.length() > fileName.length()) {
                baseDir = fileName;
            }
        }
        do {
            int index = baseDir.lastIndexOf(File.separatorChar);
            if (index <= 0) {
                baseDir = "";
            } else {
                boolean found = true;
                baseDir = baseDir.substring(0, index);
                for (String fileName : files) {
                    if (!fileName.startsWith(baseDir)) {
                        found = false;
                        break;
                    }
                }
                if (found) {
                    break;
                }
            }
        } while (StringUtils.isNotEmpty(baseDir));
        return baseDir;
    }

    private static void addCompressFile(String parentPath, File file, ZipOutputStream outputStream) throws IOException {
        if (file.isDirectory()) {
            File[] children = file.listFiles();
            if (children == null) {
                return;
            }
            String parent = String.format("%s/%s", parentPath, file.getName());
            for (File child : children) {
                addCompressFile(parent, child, outputStream);
            }
        } else {
            ZipEntry entry = new ZipEntry(String.format("%s/%s", parentPath, file.getName()));
            if (!file.exists()) {
                return;
            }
            outputStream.putNextEntry(entry);
            InputStream inputStream = null;
            try {
                inputStream = new FileInputStream(file);
                byte[] buffer = new byte[1048576];
                int len;
                do {
                    len = inputStream.read(buffer, 0, BUFFER_LEN);
                    outputStream.write(buffer, 0, len);
                } while (len == BUFFER_LEN);
            } finally {
                if (inputStream != null) inputStream.close();
            }
        }
    }
}
